﻿using System;
using System.Collections.Generic;
using ABMath.ModelFramework.Data;
using ABMath.ModelFramework.Tests;
using MathNet.Numerics.Distributions;
using MathNet.Numerics.LinearAlgebra;

namespace ABMath.ModelFramework.Visuals
{
    [Serializable]
    public class ResidualVisual : Visual
    {
        public TimeSeries TheTimeSeries
        {
            get
            {
                var ts = TheObject as TimeSeries;
                return ts;
            }
        }

        [NonSerialized]
        private Matrix quantileMatrix;
        public Matrix QuantileMatrix
        {
            get { return quantileMatrix; } 
            protected set { quantileMatrix = value; } }

        public int LjungBoxRange { get; set; }

        public List<IHypothesisTest> ResidualTests;

        public ResidualVisual()
        {
            LjungBoxRange = 40;
            ResidualTests = new List<IHypothesisTest>();

            ResidualTests.Add(new LjungBoxTest(LjungBoxRange));
            ResidualTests.Add(new McLeodLiTest(LjungBoxRange));
            ResidualTests.Add(new TurningPointTest());
            ResidualTests.Add(new BrockwellTest(12));
            ResidualTests.Add(new BrockwellTest(24));
        }

        public override string GetDescription()
        {
            return "Residual analysis";
        }

        public override string GetShortDescription()
        {
            return "Residuals";
        }

        protected override bool OnInputChanged()
        {
            var ts = TheObject as TimeSeries;
            if (ts == null)
                return false;

            // recompute everything
            Recompute(ts);
            if (associatedControl != null)
                associatedControl.DataHasChanged();
            return true;
        }


        private void Recompute(TimeSeries residuals)
        {
            var values = new List<double>();
            for (int t = 0; t < residuals.Count; ++t)
                if (!double.IsNaN(residuals[t]))
                    values.Add(residuals[t]);

            // Step 1. Build matrix of order statistics / quantiles for the QQ plot.
            var sd = new StandardDistribution();

            QuantileMatrix = new Matrix(values.Count, 2);

            var aValues = new double[values.Count];
            values.CopyTo(aValues);
            Array.Sort(aValues);

            for (int i = 0; i < values.Count; ++i)
            {
                double alpha = (i + 0.5) / values.Count;
                QuantileMatrix[i, 1] = aValues[i];
                QuantileMatrix[i, 0] = sd.InverseCumulativeDistribution(alpha);
            }

            // Step 2. Carry out some tests.
            int h = LjungBoxRange;
            Vector acf = residuals.ComputeACF(h+1, true);

            foreach (var test in ResidualTests)
            {
                if (test.GetInputType() == TestUsesType.SampleACF)
                    test.RecomputeFor(acf, residuals.Count);
                else
                    test.RecomputeFor(residuals, residuals.Count);
            }
        }

        public override List<Type> GetAllowedInputTypesFor(int socket)
        {
            if (socket != 0)
                throw new SocketException();
            return new List<Type> { typeof(TimeSeries) };
        }

    }
}
